/* BasicPseudoClassDefs.java

	Purpose:
		
	Description:
		
	History:
		Mar 20, 2012 Created by pao

Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/
	// ported from zk 6.0.0
// original package: org.zkoss.zk.ui.select
package org.zkoss.zats.common.select.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * The default set of pseudo classes in Selector.
 * @since 6.0.0
 * @author simonpai
 */
public class BasicPseudoClassDefs
{

	private final static Map<String, PseudoClassDef> _defs = new HashMap<String, PseudoClassDef>();

	static
	{

		// :root
		_defs.put("root", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				if(parameters.length > 0)
					return false;
				return ctx.getComponent().getParent() == null;
			}
		});

		// :first-child
		_defs.put("first-child", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				if(parameters.length > 0)
					return false;
				return ctx.getComponentChildIndex() == 0;
			}
		});

		// :last-child
		_defs.put("last-child", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				if(parameters.length > 0)
					return false;
				return ctx.getComponentChildIndex() + 1 == ctx.getComponentSiblingSize();
			}
		});

		// :only-child
		_defs.put("only-child", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				if(parameters.length > 0)
					return false;
				return ctx.getComponentSiblingSize() == 1;
			}
		});

		// :empty
		_defs.put("empty", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				if(parameters.length > 0)
					return false;
				return ctx.getComponent().getChildren().isEmpty();
			}
		});

		// :nth-child(n)
		_defs.put("nth-child", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				return parameters.length == 1 && acceptNthPattern(ctx.getComponentChildIndex() + 1, parameters[0]);
			}
		});

		// :nth-last-child(n)
		_defs.put("nth-last-child", new PseudoClassDef()
		{
			public boolean accept(ComponentMatchCtx ctx, String... parameters)
			{
				return parameters.length == 1 && acceptNthPattern(ctx.getComponentSiblingSize() - ctx.getComponentChildIndex(), parameters[0]);
			}
		});

	}

	/**
	 * Returns the pseudo class definition associated with the given name
	 * @param name the pseudo class name
	 * @return a pseudo class definition
	 */
	public static PseudoClassDef getDefinition(String name)
	{
		return _defs.get(name);
	}

	// helper //
	private static boolean acceptNthPattern(int index, String pattern)
	{
		return "odd".equals(pattern) && index % 2 == 1 || "even".equals(pattern) && index % 2 == 0 || new NthChildPattern(pattern).accept(index);
	}

	private static class NthChildPattern
	{

		private final int _preNum;
		private final int _postNum;
		private final boolean _valid;

		private NthChildPattern(String pattern)
		{

			int npos = pattern.indexOf('n');
			String preStr = npos < 0 ? "" : pattern.substring(0, npos);
			String postStr = npos < 0 ? pattern : pattern.substring(npos + 1);

			_valid = Pattern.matches("(?:\\+|-)?\\d*", preStr) && Pattern.matches("(?:(?:\\+|-)?\\d+)?", postStr);

			_preNum = _valid ? value(preStr, npos < 0 ? 0 : 1) : -1;
			_postNum = _valid ? value(postStr, 0) : -1;
		}

		private boolean accept(int index)
		{
			if(!_valid)
				return false;
			if(_preNum == 0)
				return index == _postNum;
			int diff = index - _postNum;
			return diff % _preNum == 0 && diff / _preNum >= 0;
		}

		private int value(String str, int defValue)
		{
			if(str.length() <= 0)
				return defValue;
			char p = str.charAt(0);
			String s = (p == '+' || p == '-') ? str.substring(1) : str;
			return (p == '-' ? -1 : 1) * (s.length() <= 0 ? defValue : Integer.valueOf(s));
		}

	}

}
